#pragma once
#include <string>
#include <rapidxml.hpp>
#include <ExtXml.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sstream>
#include <iostream>
#include "NetModel.h"
#include "ViewerException.h"

struct XMLNetworkSnapshotParser {

  static void parse(rapidxml::xml_document<>& netDoc, 
                    NetworkSnapshotPtr snapshot, std::ostream& errs) {
    using namespace rapidxml;
    try {
      xml_node<> *rootXMLNode = netDoc.first_node("networks");
      if (!rootXMLNode) { throw ViewerException("NetData: no proper XML root"); }

      for(xml_node<> *networkXMLNode = rootXMLNode->first_node("network"); 
          networkXMLNode;
          networkXMLNode = networkXMLNode->next_sibling("network")) {
       
        std::string networkId = 
          getStringAttribute(networkXMLNode, "id");
        if (!networkId.size()) { 
          errs << "Network with no id" << std::endl;
          continue; 
        }
        Network network(networkId);

        for(xml_node<> *subNetXMLNode = networkXMLNode->first_node("subnet");
            subNetXMLNode; 
            subNetXMLNode = subNetXMLNode->next_sibling("subnet")) {

          std::string subNetId = 
              getStringAttribute(subNetXMLNode, "id");
          if (!subNetId.size()) {
            errs << "Subnet with no id" << std::endl;
            continue;
          }

          NetAddress subNetMask(
            NA_IPV4, 
            getIpAttribute(subNetXMLNode, "mask", "Subnet mask", errs)
          );
          
          SubNet subNet(subNetId, subNetMask);

          for(xml_node<> *netNodeXMLNode = subNetXMLNode->first_node("node");
              netNodeXMLNode;
              netNodeXMLNode = netNodeXMLNode->next_sibling("node")) {
            
            NetAddress nodeAddress(
                NA_IPV4, 
                getIpAttribute(netNodeXMLNode, "ip", "Network node", errs)
            ); 
            NetNode netNode(nodeAddress);
            subNet.addNode(netNode);
          }

          network.addSubNet(subNet);
        }

        for(xml_node<> *routerXMLNode = networkXMLNode->first_node("router"); 
            routerXMLNode; 
            routerXMLNode = routerXMLNode->next_sibling("router")) {

          NetRouter router(getStringAttribute(routerXMLNode, "descr"));

          for(xml_node<> *interfaceXMLNode = routerXMLNode->first_node("interface");
              interfaceXMLNode; 
              interfaceXMLNode = interfaceXMLNode->next_sibling("interface")) {
            
            NetAddress interfaceAddr(NA_IPV4,
              getIpAttribute(interfaceXMLNode, "ip", "Interface", errs),
              getIpAttribute(interfaceXMLNode, "mask", "Interface mask", errs)
            );

            SubNet::Id interfaceSubNetId = 
              SubNet::strToId(getStringAttribute(interfaceXMLNode, "subnet-id"));

            NetInterface netInterface(
              interfaceAddr, 
              getStringAttribute(interfaceXMLNode, "name"),
              interfaceSubNetId,
              getSizeTAttribute(interfaceXMLNode, "inBytes", 
                "Interface inBytes", errs),
              getSizeTAttribute(interfaceXMLNode, "outBytes",
                "Interface outBytes", errs)
            );

            router.addInterface(netInterface);
          }
          network.addRouter(router);
        }
        snapshot->addNetwork(network);
      }
    } catch(parse_error& ex) {
      errs << "Network data XML parse error: " << ex.what() << std::endl;
    }
  }

  private:
  static std::string getStringAttribute(rapidxml::xml_node<> *node, 
                                        const std::string& attrName) {
    using namespace rapidxml;
    xml_attribute<>* tempXMLAttribute = NULL;
    std::string attrStr;
    tempXMLAttribute = node->first_attribute(attrName.c_str());
    if (tempXMLAttribute) {
      attrStr = tempXMLAttribute->value();
    }
    return attrStr;
  }

  static IpV4Addr getIpAttribute(rapidxml::xml_node<> *node, const std::string& attrName,
                                 const std::string& errPrefix, std::ostream& errs) {
    using namespace rapidxml;
    std::string attrStr = 
      getStringAttribute(node, attrName);
    IpV4Addr attrIP = INADDR_NONE;
    if (attrStr.size()) {
      attrIP = inet_addr(attrStr.c_str());
      if (attrIP == INADDR_NONE) {
        errs << errPrefix
          << " IP address is invalid" 
          << std::endl;
      }
    }
    return attrIP;
  }

  static size_t getSizeTAttribute(rapidxml::xml_node<> *node, const std::string& attrName,
                                  const std::string& errPrefix, std::ostream& errs) {
    using namespace rapidxml;
    std::string attrStr = 
      getStringAttribute(node, attrName);
    size_t attrSizeT = 0;
    if (attrStr.size()) {
      std::istringstream bufferStr(attrStr);
      bufferStr >> attrSizeT;
      if (bufferStr.fail()) {
        errs << errPrefix
          << " number is invalid" << std::endl;
      }
    }
    return attrSizeT;
  }
};
